﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Web.UI.WebControls;
using umbraco;
using umbraco.BusinessLogic;
using umbraco.cms.businesslogic.media;
using SD = System.Drawing;

namespace ProlificNotion.Umbraco.MediaImporter
{
    public partial class ImportMedia : System.Web.UI.Page
    {
        private const String iconDir = "~/umbraco/images/importMedia/";
        private const String iconFolder = iconDir + "folder.png";
        private const String iconFolderOpen = iconDir + "folderOpen.png";

        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);
            FolderMapping = new Dictionary<string, int>();
            DirTreeView.TreeNodeCheckChanged += new TreeNodeEventHandler(DirTreeView_TreeNodeCheckChanged);
        }

        /// <summary>
        /// Handles the TreeNodeCheckChanged event of the DirTreeView control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Web.UI.WebControls.TreeNodeEventArgs"/> instance containing the event data.</param>
        protected void DirTreeView_TreeNodeCheckChanged(object sender, TreeNodeEventArgs e)
        {
            ToggleChildren(e.Node, e.Node.Checked);
        }

        /// <summary>
        /// Handles the SelectedNodeChanged event of the DirTreeView control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        protected void DirTreeView_SelectedNodeChanged(object sender, EventArgs e)
        {
            DirTreeView.SelectedNode.Checked = !DirTreeView.SelectedNode.Checked;
        }

        /// <summary>
        /// Handles the Load event of the Page control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        protected void Page_Load(object sender, EventArgs e)
        {
            if (!Page.IsPostBack)
            {
                PopulateTreeView();
            }
        }

        /// <summary>
        /// Populates the tree view.
        /// </summary>
        private void PopulateTreeView()
        {
            TreeNode rootNode;

            foreach (string dir in Config.ImportFolders)
            {
                DirectoryInfo info = new DirectoryInfo(Server.MapPath(dir));
                if (info.Exists)
                {
                    rootNode = new TreeNode(info.Name);
                    rootNode.Value = info.FullName;
                    rootNode.ImageUrl = iconFolder;
                    rootNode.Expanded = false;
                    rootNode.ShowCheckBox = true;
                    rootNode.SelectAction = TreeNodeSelectAction.SelectExpand;


                    GetDirectories(info.GetDirectories(), rootNode);
                    GetFiles(info, rootNode);

                    DirTreeView.Nodes.Add(rootNode);
                }
            }
        }

        /// <summary>
        /// Recursive .
        /// </summary>
        /// <param name="node">The node.</param>
        /// <param name="nodeIsChecked">if set to <c>true</c> [node is checked].</param>
        private void ToggleChildren(TreeNode node, bool nodeIsChecked)
        {
            foreach (TreeNode child in node.ChildNodes)
            {
                child.Checked = nodeIsChecked;
                if (child.ChildNodes.Count > 0)
                {
                    //Has children loop again
                    ToggleChildren(child, nodeIsChecked);
                }
            }
        }

        /// <summary>
        /// Gets the directories.
        /// </summary>
        /// <param name="subDirs">The sub directories to add to the node.</param>
        /// <param name="nodeToAddTo">The node to add the subdirectories to.</param>
        private void GetDirectories(DirectoryInfo[] subDirs, TreeNode Parent)
        {
            TreeNode aNode;
            DirectoryInfo[] subSubDirs;

            foreach (DirectoryInfo subDir in subDirs)
            {
                aNode = new TreeNode(subDir.Name, subDir.FullName);
                aNode.Value = subDir.FullName;
                aNode.ImageUrl = iconFolder;
                aNode.Expanded = false;
                aNode.ShowCheckBox = true;

                GetFiles(subDir, aNode);

                Parent.ChildNodes.Add(aNode);

                subSubDirs = subDir.GetDirectories();
                if (subSubDirs.Length != 0)
                {
                    GetDirectories(subSubDirs, aNode);
                }
            }
        }

        /// <summary>
        /// Handles the Click event of the importButton control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        protected void importButton_Click(object sender, EventArgs e)
        {
            Import(null);
        }

        /// <summary>
        /// Imports the media into the specified parent media item.
        /// </summary>
        /// <param name="parent">The parent media item.</param>
        private void Import(TreeNode parent)
        {
            TreeNodeCollection tnc;

            if (parent == null)
                tnc = DirTreeView.Nodes;
            else
                tnc = parent.ChildNodes;

            foreach (TreeNode node in tnc)
            {
                if (node.Checked)
                {
                    if (File.Exists(node.Value))
                    {
                        ImportFile(node.Value, node.Parent.Value);
                    }
                    else
                    {
                        ImportFolder(node.Value, (node.Parent == null) ? String.Empty : node.Parent.Value);
                    }
                }
                Import(node);
            }

            instructionLabel.CssClass = "success";
            instructionLabel.Text = "Media file(s) imported successfully";
            DirTreeView.Visible = false;
            importButton.Visible = false;
        }

        /// <summary>
        /// Gets the files in a given directory.
        /// </summary>
        /// <param name="Dir">The directory to get files from.</param>
        /// <param name="Parent">The parent node to append files nodes to.</param>
        private void GetFiles(DirectoryInfo Dir, TreeNode Parent)
        {
            TreeNode aNode;

            foreach (FileInfo file in Dir.GetFiles())
            {
                aNode = new TreeNode(file.Name, file.FullName);
                aNode.ImageUrl = GetFileIcon(file.Extension);
                aNode.Expanded = false;
                aNode.ShowCheckBox = true;
                aNode.Checked = Parent.Checked;

                Parent.ChildNodes.Add(aNode);
            }
        }

        /// <summary>
        /// Imports the folder.
        /// </summary>
        /// <param name="folderPath">The folder path.</param>
        /// <param name="parentPath">The parent path.</param>
        private void ImportFolder(string folderPath, string parentPath)
        {
            DirectoryInfo info = new DirectoryInfo(folderPath);
            //- Only import folder if it contains files else whats the point?
            if (info.GetFiles().Length > 0 || parentPath == String.Empty)
            {
                string folderName = info.Name;
                MediaType mt = MediaType.GetByAlias("Folder");
                Media m = Media.MakeNew(folderName, mt, umbraco.BasePages.UmbracoEnsuredPage.CurrentUser, GetParent(parentPath));

                //Add the mapping to the dictionary so we can determine the Folder ID in a later stage
                FolderMapping.Add(folderPath, m.Id);
            }
        }

        /// <summary>
        /// Creates the media node.
        /// </summary>
        /// <param name="sourceFile">The source file to import.</param>
        private void ImportFile(string sourceFile, string parentPath)
        {
            FileInfo file = new FileInfo(sourceFile);
            string filename = Path.GetFileName(sourceFile).Substring(0, Path.GetFileName(sourceFile).LastIndexOf("."));
            string extension = Path.GetExtension(sourceFile).TrimStart('.').ToLower();
            string imageFileExtensions = UmbracoSettings.ImageFileTypes.ToLower();
            int parentNodeId = GetParent(parentPath);
            string mediaFileName = Path.GetFileName(sourceFile);

            //- Set a default MediaType for imports
            MediaType mt = MediaType.GetByAlias("File");

            //- Check if we are importing an image and if so set the appropriate MediaType
            if (imageFileExtensions.IndexOf(extension) > 0)
            {
                mt = MediaType.GetByAlias("Image");
            }

            //- Create the new media item
            Media m = Media.MakeNew(filename, mt, umbraco.BasePages.UmbracoEnsuredPage.CurrentUser, parentNodeId);

            umbraco.cms.businesslogic.property.Property umbracoFile = m.getProperty("umbracoFile");

            //- Try setting some of the properties for the file
            try
            {
                umbracoFile.Value = String.Format("/media/{0}/{1}", umbracoFile.Id, mediaFileName);
                m.getProperty("umbracoBytes").Value = file.Length;
                m.getProperty("umbracoExtension").Value = Path.GetExtension(sourceFile).Replace(".", "");

                //- If we are importing an image try and set some of the image properties
                if (mt.Alias == "Image")
                {
                    // Check if image and then get sizes, make thumb and update database
                    if (m.getProperty("umbracoWidth") != null && m.getProperty("umbracoHeight") != null && imageFileExtensions.IndexOf(extension) > -1)
                    {
                        int fileWidth;
                        int fileHeight;

                        using (FileStream fs = new FileStream(sourceFile, FileMode.Open, FileAccess.Read, FileShare.Read))
                        {
                            SD.Image image = SD.Image.FromStream(fs);

                            //- Get the image dimension
                            fileWidth = image.Width;
                            fileHeight = image.Height;
                            fs.Close();

                            //- Set the image dimensions
                            m.getProperty("umbracoWidth").Value = fileWidth.ToString();
                            m.getProperty("umbracoHeight").Value = fileHeight.ToString();

                            image.Dispose();
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                //- If the process fails at this point we need to remove the media node
                //- or we end up with a node without a file
                m.delete();
                Log.Add(LogTypes.Error, parentNodeId, String.Concat("Error importing media item : ", ex.Message));
            }

            //- Create the directory on the server for the file
            DirectoryInfo dir = Directory.CreateDirectory(Server.MapPath("~/media") + "\\" + umbracoFile.Id);

            //- Move the file from the import location to the new directory
            MoveFile(sourceFile, umbracoFile.Id);

            //- Save the Media item
            m.Save();

            if (mt.Alias == "Image")
            {
                // Generate thumbnails
                GenerateThumbnail(m);
            }
        }

        /// <summary>
        /// Moves the file.
        /// </summary>
        /// <param name="source">The source.</param>
        /// <param name="target">The Id of the media item.</param>
        private void MoveFile(string source, int target)
        {
            FileInfo f = new FileInfo(source);

            try
            {
                string folderPath = f.DirectoryName;

#if DEBUG
                //- Copy the file to the new media item directory
                f.CopyTo(Server.MapPath("~/media/" + target.ToString() + "/" + Path.GetFileName(f.Name)));
#else
                //- Move the file to the new media item directory
                f.MoveTo(Server.MapPath("~/media/" + target.ToString() + "/" + Path.GetFileName(f.Name)));
                DeleteFolderWhenEmpty(folderPath);
#endif
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        /// <summary>
        /// Deletes the folder when it cointains no files or folders.
        /// </summary>
        /// <param name="folderPath">The folder path.</param>
        private void DeleteFolderWhenEmpty(string folderPath)
        {
            DirectoryInfo folder = new DirectoryInfo(folderPath);
            if (folder.GetFileSystemInfos().Length == 0)
            {
                //Folder is empty
                folder.Delete();
            }
        }

        /// <summary>
        /// Gets the redirect URL.
        /// </summary>
        /// <returns>The url to redirect to</returns>
        /// <remarks>
        /// Required as an alternative to javascript window.location as it will keep 
        /// appending to the existing querystring otherwise
        /// </remarks>
        private string GetUrl()
        {
            return String.Concat(Request.ServerVariables["url"], "?id=", Request.QueryString["id"]);
        }

        /// <summary>
        /// Gets the file icon.
        /// </summary>
        /// <param name="extension">The extension.</param>
        /// <returns>The path to the file icon</returns>
        private string GetFileIcon(String extension)
        {
            //- Remove the dot if it exists
            extension = extension.TrimStart('.').ToLower();

            //- Get ImageFileTypes from the Umbraco Settings
            ArrayList imgExt = new ArrayList(UmbracoSettings.ImageFileTypes.Split(','));

            if (imgExt.Contains(extension))
                return String.Concat(iconDir, "picture.png");

            if (File.Exists(String.Concat(iconDir, extension)))
                return String.Concat(extension, ".png");
            else
                return String.Concat(iconDir, "file.png");
        }

        /// <summary>
        /// Generates the thumbnail.
        /// </summary>
        /// <param name="sourceFile">The source file.</param>
        /// <param name="fullFilePath">The full file path.</param>
        /// <param name="ext">The ext.</param>
        /// <param name="thumbnailFileName">Name of the thumbnail file.</param>
        /// <remarks>Contributed by Richard Soeteman</remarks>
        private void GenerateThumbnail(Media media)
        {
            int maxWidthHeight = Config.ThumbnailMaxWidthHeight;
            string thumbnailFilename = String.Empty;
            string umbracoFilename = String.Empty;
            string umbracoExtension = String.Empty;

            if (media != null)
            {
                umbracoFilename = media.getProperty("umbracoFile").Value.ToString();
                umbracoExtension = media.getProperty("umbracoExtension").Value.ToString();
                thumbnailFilename = umbracoFilename.Replace("." + umbracoExtension, "_thumb.jpg");
                
                using (FileStream fs = new FileStream(Server.MapPath(umbracoFilename), FileMode.Open, FileAccess.Read, FileShare.Read))
                {
                    SD.Image image = SD.Image.FromStream(fs);

                    //- Get the image dimension
                    int fileWidth = image.Width;
                    int fileHeight = image.Height;
                    fs.Close();

                    // Generate thumbnail
                    float fx = (float)fileWidth / (float)maxWidthHeight;
                    float fy = (float)fileHeight / (float)maxWidthHeight;
                    // must fit in thumbnail size
                    float f = Math.Max(fx, fy); //if (f < 1) f = 1;
                    int widthTh = (int)Math.Round((float)fileWidth / f); int heightTh = (int)Math.Round((float)fileHeight / f);

                    // fixes for empty width or height
                    if (widthTh == 0)
                        widthTh = 1;
                    if (heightTh == 0)
                        heightTh = 1;

                    // Create new image with best quality settings
                    Bitmap bp = new Bitmap(widthTh, heightTh);
                    Graphics g = Graphics.FromImage(bp);
                    g.SmoothingMode = SmoothingMode.HighQuality;
                    g.InterpolationMode = InterpolationMode.HighQualityBicubic;
                    g.PixelOffsetMode = PixelOffsetMode.HighQuality;

                    // Copy the old image to the new and resized
                    Rectangle rect = new Rectangle(0, 0, widthTh, heightTh);
                    g.DrawImage(image, rect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel);

                    // Copy metadata
                    ImageCodecInfo[] codecs = ImageCodecInfo.GetImageEncoders();
                    ImageCodecInfo codec = null;
                    for (int i = 0; i < codecs.Length; i++)
                    {
                        if (codecs[i].MimeType.Equals("image/jpeg"))
                            codec = codecs[i];
                    }

                    // Set compresion ratio to 90%
                    EncoderParameters ep = new EncoderParameters();
                    ep.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 90L);

                    // Save the new image
                    bp.Save(Server.MapPath(thumbnailFilename), codec, ep);
                    bp.Dispose();
                    g.Dispose();
                }
            }
        }

        /// <summary>
        /// Gets the parent folder.
        /// </summary>
        /// <param name="folderName">Name of the folder.</param>
        /// <returns>The parent id</returns>
        private int GetParent(string folderName)
        {
            if (FolderMapping.ContainsKey(folderName))
            {
                return FolderMapping[folderName];
            }
            return int.Parse(Request.QueryString["id"]);
        }

        /// <summary>
        /// Gets or sets the folder mapping.
        /// </summary>
        /// <value>The folder mapping.</value>
        private Dictionary<string, int> FolderMapping { get; set; }

    }
}
